// -*- coding: utf-8 -*-

/**
 * @file hybrid_solver.h
 * @brief Classes for search algorithm for hybrid problems using Elisa and HODES.
 */

/**
 * @defgroup Hybrid Hybrid
 * @brief An extension classes for hybrid problems
 */

#ifndef _HYBRID_SOLVER_H
#define _HYBRID_SOLVER_H

#include <iostream>
#include <fstream>
#include <boost/shared_ptr.hpp>

#include "vnode.h"

#include "elisa/elisa_defs.h"
#include "elisa/elisa_solver.h"

#include "hybrid_config.h"
#include "hybrid_model.h"

NAMESPACE_ELISA

#ifdef HYBRID_PROFILE
extern long numSplitTime;
extern long numHybridReduction;
extern long numNarrow;
extern long numNewton;
#endif

/**
 * @ingroup Hybrid
 * @class HybridMakeNeighborhood
 */
class HybridMakeNeighborhood : public MakeNeighborhood
{
public:
	HybridMakeNeighborhood(ContConstraintRep& cc)
		: MakeNeighborhood(),
		  m_cc(cc)
	{}

	/**
 	 * Make the neighbors of this box
 	 */
	virtual bool operator()();

protected:
	ContConstraintRep& m_cc;
};

/**
 * @ingroup Hybrid
 * @class HybridChoise
 * @brief Choice of a variable in a hybrid model
 */
class HybridChoise : public ChoiceStrategy
{
public:

	/**
 	 * Constructor
 	 */
	HybridChoise(SplitChooser *s = 0)
		: ChoiceStrategy(s)
	{
	}

	/**
 	 * Destructor
 	 */
	~HybridChoise()
	{
	}

	/**
 	 * Return the last chosen variable
 	 */
	//VarImplPtr getLastVar();

	/**
 	 * Choice function
 	 * @return the VariableImplementation next after the last chosen VariableImplementation
 	 */
	VarImplPtr get() throw (SplitChooser::NoSplitableVar);

private:
	// @var last chosen variable
	VarImplPtr m_var;

	// @var the corresponding iterator in the model
	VarImplSet::const_iterator m_it;
};

/**
 * @class BackTrack
 * strategy to do backtracking in the SearchTree
 */
class HybridBackTrack : public BackTrack
{
public:
	HybridBackTrack(Model & model) : m_model(model) {}

	/**
 	 * strategy algorithm
 	 */
	virtual bool operator()()
	{
#ifdef HYBRID_LOG
		if (m_st->getCurrent()->getNumChildren() == 0) {
			VariableSet vs;
			m_model.toVariables(vs);
			VariableSet::iterator vit = vs.begin();
			/*
			for ( ; vit == vs.end(); ++vit) {
				if ((*vit)->getType() == "TimeImpl")
					break;
			}

			Time & time(*(dynamic_cast<Time *>((*vit)->getSymbolicRep())));
			v_bias::interval t = v_bias::interval(time.domain().getTime());
			const iVector & iv = dynamic_cast<TimeImpl *>((*vit).get())->domain().getIV();
			const iVector & ae = dynamic_cast<TimeImpl *>((*vit).get())->domain().getAE();
			*/
			++vit;
			++vit;
			VarImplPtr vi2 =  *vit;
			++vit;
			VarImplPtr vi1 =  *vit;

			
			static std::ofstream fot1("tight2.dat");
			static std::ofstream fot2("interval2.dat");
			//printAsRect(fot1, iv[0] + interval(-0.01, 0.01), iv[2] + interval(-0.01, 0.01));
			printAsRect(fot1, *vi1->getSearchTree()->domain<Interval>(vi1->getIndexDomain()), 
							  *vi2->getSearchTree()->domain<Interval>(vi2->getIndexDomain()));
			//printAsRect(fot2, t, ae[ContVar::logIndex]);
			//printAsRect(fot2, ae[0], ae[2]);
		}
#endif

		return BackTrack::operator()();
	}

	Model & m_model;
};


typedef boost::shared_ptr<vnodelp::VNODE> VnodePtr;

/**
 * @ingroup Hybrid
 * @class HybridSolver:
 * @brief A solver based on hybrid branch-and-prune algorithm
 */
class HybridSolver : public Solver
{
public:
	/**
 	 * Constructor: each solver is associated to a model
 	 * @param mo Constraints' model
 	 */
	HybridSolver (Model & model, ContConstraint & cc)
		: Solver(new Model(model),
				 new HybridBackTrack(model), new Move, 
				 //new HybridMakeNeighborhood(*(dynamic_cast<ContConstraintRep *>(cc.rep()))))
				 new MakeNeighborhood())
	{
		Solver::useChoiceStrategy(new HybridChoise());
		//Solver::useChoiceStrategy(new SearchRoundRobin());
		//Solver::useChoiceStrategy(new SearchMaxInterval());

		// TODO
		m_odeSolver = VnodePtr(new vnodelp::VNODE(dynamic_cast<ContConstraintRep *>(cc.rep())->getAD().get()));
		//m_odeSolver.setTols(1e-10, 1e-10);
		m_odeSolver->setTols(1e-20, 1e-20);
		m_odeSolver->setOrder(20);
		//m_odeSolver->setOrder(50);
		//m_odeSolver->setHmin(1e-13);
		m_odeSolver->setHmin(1e-11);
		m_odeSolver->setOneStep(vnodelp::on);
	}

	/**
 	 * Destructor
 	 */
	~HybridSolver ()
	{
	}

	static VnodePtr getOdeSolver()
	{
		return m_odeSolver;
	}

protected:
	static VnodePtr m_odeSolver;
};

END_NAMESPACE_ELISA

#endif // HYBRID_SOLVER
